/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.util;

import java.nio.charset.Charset;

import org.zhiqim.git.GitConstants;
import org.zhiqim.git.GitI18n;

import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.mutables.MInt;

/**
 * Git字节数组工具类
 *
 * @version v1.0.0 @author zouzhigang 2017-3-31 新建与整理
 */
public class GitBytes implements GitConstants
{
    /***********************************************************************************************/
    //对象信息段落&编码
    /***********************************************************************************************/
    
    /**
     * 从缓冲中找到新段落起始位置，操作方法为找到空行
     * 
     * @param b         字节数组
     * @param off       偏移量
     * @return          返回消息起始位置
     */
    public static int paragraph(byte[] b, int off)
    {
        //跳过所有非空行
        while (off < b.length && b[off] != '\n'){
            off = Bytes.nextLF(b, off);
        }
        
        return off;
    }
    
    /**
     * 从缓冲中找到对应"encoding "开头的行，encoding后面的即为字符编码，没有默认UTF-8
     * 
     * @param b     缓存
     * @return      字符集
     */
    public static Charset encoding(byte[] b)
    {
        int off = encoding(b, 0);
        if (off < 0)
            return _UTF_8_C_;
        
        int end = Bytes.nextLF(b, off) - 1;
        String charset = new String(b, off, end-off, _UTF_8_C_);
        return Charset.forName(charset);
    }
    
    /**
     * 从缓冲中找到对应"encoding "开头的行，encoding后面的即为字符编码
     *  
     * @param b         字节数组
     * @param off       偏移量
     * @return          返回到字符编码的位置
     */
    private static int encoding(byte[] b, int off)
    {
        while (off < b.length)
        {
            if (b[off] == '\n')
                return -1;
            
            if (b[off] == 'e')
                break;
            
            off = Bytes.nextLF(b, off);
        }
        
        return Bytes.match(b, off, P_ENCODING);
    }
    
    /**
     * 读取长度值，按高1位=1判断是否还有后续字节，后续字节采用右移7位进制
     * 
     * @param data      数据
     * @param off       偏移
     * @return          长度值
     */
    public static final int readLength(byte[] data, MInt off)
    {
        int c, shift = 0, len = 0;
        
        do
        {
            c = data[off.value++] & 0xFF;
            len |= (c & 0x7F) << shift;
            shift += 7;
        }
        while ((c & 0x80) != 0);
        
        return len;
    }

    /**
     * 合并基础对象和增量对象的数据成一个完整的对象数据
     * 
     * @param base      基础对象数据
     * @param delta     增量对象数据
     * @return          完整对象数据
     */
    public static final byte[] merge(final byte[] base, final byte[] delta)
    {
        MInt off = new MInt();
        
        //1.读出增量数据中定义的基础长度，并比较是否一致
        int baseLen = readLength(delta, off);
        Asserts.asserts(base.length == baseLen, GitI18n.baseLengthIncorrect);
        
        //2.读取目标数据长度
        int resultLen = readLength(delta, off);
        byte[] result = new byte[resultLen];
        
        int resultOff = 0;
        while (off.value < delta.length)
        {
            final int cmd = delta[off.value++] & 0xFF;
            if ((cmd & 0x80) != 0)
            {//首位=1表示读取基础数据段，剩余7位，低4位表示offset，高3位表示len
                int offset = 0;
                if ((cmd & 0x01) != 0)
                    offset = delta[off.value++] & 0xFF;
                if ((cmd & 0x02) != 0)
                    offset |= (delta[off.value++] & 0xFF) << 8;
                if ((cmd & 0x04) != 0)
                    offset |= (delta[off.value++] & 0xFF) << 16;
                if ((cmd & 0x08) != 0)
                    offset |= (delta[off.value++] & 0xFF) << 24;

                int len = 0;
                if ((cmd & 0x10) != 0)
                    len = delta[off.value++] & 0xFF;
                if ((cmd & 0x20) != 0)
                    len |= (delta[off.value++] & 0xFF) << 8;
                if ((cmd & 0x40) != 0)
                    len |= (delta[off.value++] & 0xFF) << 16;
                
                if (len == 0)
                    len = 0x10000;

                System.arraycopy(base, offset, result, resultOff, len);
                resultOff += len;
            }
            else if (cmd != 0)
            {//读取增量数据段长度
                System.arraycopy(delta, off.value, result, resultOff, cmd);
                off.value += cmd;
                resultOff += cmd;
            }
            else
            {//cmd == 0不支持
                throw Asserts.exception("不支持的数据格式");
            }
        }

        return result;
    }
}
